import path from 'path'
import {
  createReactCodeConnect,
  generateExpressionFromIntrinsic,
  getSetOfAllPropsReferencedInPropMapping,
} from '../create'
const fs = require('fs')
import prettier from 'prettier'
import { IntrinsicKind } from '../../connect/intrinsics'

jest.mock('fs')

describe('createReactCodeConnect', () => {
  beforeEach(() => {
    jest.resetAllMocks()
  })
  it('Should generate a boolean variant if the variant options are all boolean', async () => {
    fs.existsSync.mockReturnValue(false)

    await createReactCodeConnect({
      destinationDir: 'test',
      destinationFile: 'test.figma.tsx',
      config: { parser: 'react' },
      mode: 'CREATE',
      normalizedName: 'Test',
      figmaConnections: [
        {
          component: {
            id: '1:1',
            figmaNodeUrl: 'fake-url',
            name: 'Test',
            type: 'COMPONENT_SET',
            componentPropertyDefinitions: {
              BooleanVariant: {
                type: 'VARIANT',
                defaultValue: 'true',
                variantOptions: ['true', 'false'],
              },
              BooleanVariant2: {
                type: 'VARIANT',
                defaultValue: 'True',
                variantOptions: ['True', 'False'],
              },
              BooleanVariant3: {
                type: 'VARIANT',
                defaultValue: 'yes',
                variantOptions: ['yes', 'no'],
              },
              BooleanVariant4: {
                type: 'VARIANT',
                defaultValue: 'Yes',
                variantOptions: ['Yes', 'No'],
              },
              BooleanVariant5: {
                type: 'VARIANT',
                defaultValue: 'on',
                variantOptions: ['on', 'off'],
              },
              BooleanVariant6: {
                type: 'VARIANT',
                defaultValue: 'On',
                variantOptions: ['On', 'Off'],
              },
              Variant: {
                type: 'VARIANT',
                defaultValue: 'Yes',
                variantOptions: ['Yes', 'No', 'Intermediate'],
              },
              Variant2: {
                type: 'VARIANT',
                defaultValue: 'Yes',
                variantOptions: ['True', 'SomethingElse'],
              },
            },
          },
        },
      ],
    })

    const expected = await prettier.format(
      `\
import React from "react"
import { Test } from "./Test"
import figma from "@figma/code-connect"

/**
 * -- This file was auto-generated by Code Connect --
 * \`props\` includes a mapping from Figma properties and variants to
 * suggested values. You should update this to match the props of your
 * code component, and update the \`example\` function to return the
 * code example you'd like to see in Figma
*/

figma.connect(Test, "fake-url", {
  props: {
    booleanVariant: figma.boolean("BooleanVariant"),
    booleanVariant2: figma.boolean("BooleanVariant2"),
    booleanVariant3: figma.boolean("BooleanVariant3"),
    booleanVariant4: figma.boolean("BooleanVariant4"),
    booleanVariant5: figma.boolean("BooleanVariant5"),
    booleanVariant6: figma.boolean("BooleanVariant6"),
    variant: figma.enum("Variant", {
      Yes: "yes",
      No: "no",
      Intermediate: "intermediate",
    }),
    variant2: figma.enum("Variant2", {
      True: "true",
      SomethingElse: "somethingelse",
    }),
  },
  example: (props) => <Test />,
})`,
      {
        parser: 'typescript',
        semi: false,
        trailingComma: 'all',
      },
    )

    expect(fs.writeFileSync).toHaveBeenCalledWith('test.figma.tsx', expected)
  })

  it('Should show the no-props message if no props could be mapped', async () => {
    fs.existsSync.mockReturnValue(false)

    await createReactCodeConnect({
      destinationDir: 'test',
      destinationFile: 'test.figma.tsx',
      config: { parser: 'react' },
      mode: 'CREATE',
      normalizedName: 'Test',
      figmaConnections: [
        {
          propMapping: {},
          reactTypeSignature: {
            someBool: 'false | true',
          },
          component: {
            id: '1:1',
            figmaNodeUrl: 'fake-url',
            name: 'Test',
            type: 'COMPONENT_SET',
            componentPropertyDefinitions: {
              Label: {
                type: 'TEXT',
                defaultValue: 'Some label',
              },
            },
          },
        },
      ],
    })

    const expected = await prettier.format(
      `\
import React from "react"
import { Test } from "./Test"
import figma from "@figma/code-connect"

/**
 * -- This file was auto-generated by Code Connect --
 * None of your props could be automatically mapped to Figma properties.
 * You should update the \`props\` object to include a mapping from your
 * code props to Figma properties, and update the \`example\` function to
 * return the code example you'd like to see in Figma
 */

figma.connect(Test, "fake-url", {
  props: {
    // No matching props could be found for these Figma properties:
    // \"label\": figma.string('Label')
  },
  example: (props) => <Test
          someBool={/* TODO */}/>,
})`,
      {
        parser: 'typescript',
        semi: false,
        trailingComma: 'all',
      },
    )

    expect(fs.writeFileSync).toHaveBeenCalledWith('test.figma.tsx', expected)
  })

  it('Should use any available prop mappings', async () => {
    fs.existsSync.mockReturnValue(false)

    await createReactCodeConnect({
      destinationDir: 'test',
      destinationFile: 'test.figma.tsx',
      config: { parser: 'react' },
      mode: 'CREATE',
      normalizedName: 'Test',
      figmaConnections: [
        {
          propMapping: {
            actualPropNameForhasIcon: {
              kind: IntrinsicKind.Boolean,
              args: {
                figmaPropName: 'Has icon',
              },
            },
            actualPropNameForTitle: {
              kind: IntrinsicKind.Boolean,
              args: {
                figmaPropName: 'Should show title',
                valueMapping: {
                  true: {
                    kind: IntrinsicKind.String,
                    args: {
                      figmaPropName: 'Label',
                    },
                  },
                  false: undefined,
                },
              },
            },
          },
          reactTypeSignature: {
            actualPropNameForhasIcon: 'false | true',
            actualPropNameForTitle: '?string',
            notMappedProp: 'number',
          },
          component: {
            id: '1:1',
            figmaNodeUrl: 'fake-url',
            name: 'Test',
            type: 'COMPONENT_SET',
            componentPropertyDefinitions: {
              BooleanVariant: {
                type: 'VARIANT',
                defaultValue: 'true',
                variantOptions: ['true', 'false'],
              },
              'Should show title': {
                type: 'VARIANT',
                defaultValue: 'true',
                variantOptions: ['true', 'false'],
              },
              'Figma-only variant': {
                type: 'VARIANT',
                defaultValue: 'this',
                variantOptions: ['this', 'that'],
              },
              Label: {
                type: 'TEXT',
                defaultValue: 'Some label',
              },
              'Some other text': {
                type: 'TEXT',
                defaultValue: '',
              },
              'Has icon': {
                type: 'BOOLEAN',
                defaultValue: false,
              },
            },
          },
        },
      ],
    })

    const expected = await prettier.format(
      `\
import React from "react"
import { Test } from "./Test"
import figma from "@figma/code-connect"

/**
 * -- This file was auto-generated by Code Connect --
 * \`props\` includes a mapping from your code props to Figma properties.
 * You should check this is correct, and update the \`example\` function
 * to return the code example you'd like to see in Figma
 */

figma.connect(Test, "fake-url", {
  props: {
    // These props were automatically mapped based on your linked code:
    actualPropNameForhasIcon: figma.boolean(\"Has icon\"),
    actualPropNameForTitle: figma.boolean(\"Should show title\", {
      true: figma.string(\"Label\"),
      false: undefined,
    }),
    // No matching props could be found for these Figma properties:
    // \"booleanVariant\": figma.boolean('BooleanVariant'),
    // \"figmaonlyVariant\": figma.enum('Figma-only variant', {
    //   \"this\": \"this\",
    //   \"that\": \"that\"
    // }),
    // \"someOtherText\": figma.string('Some other text')
  },
  example: (props) => <Test
          actualPropNameForhasIcon={props.actualPropNameForhasIcon}
          actualPropNameForTitle={props.actualPropNameForTitle}
          notMappedProp={/* TODO */}/>,
})`,
      {
        parser: 'typescript',
        semi: false,
        trailingComma: 'all',
      },
    )

    expect(fs.writeFileSync).toHaveBeenCalledWith('test.figma.tsx', expected)
  })

  it('should only generate required props in the example function', async () => {
    fs.existsSync.mockReturnValue(false)

    await createReactCodeConnect({
      destinationDir: 'test',
      destinationFile: 'test.figma.tsx',
      config: { parser: 'react' },
      mode: 'CREATE',
      normalizedName: 'Test',
      figmaConnections: [
        {
          reactTypeSignature: {
            nonOptionalProp: 'false | true',
            optionProp: '?string',
          },
          component: {
            id: '1:1',
            figmaNodeUrl: 'fake-url',
            name: 'Test',
            type: 'COMPONENT_SET',
            componentPropertyDefinitions: {},
          },
        },
      ],
    })

    const expected = await prettier.format(
      `\
      import React from "react"
      import { Test } from "./Test"
      import figma from "@figma/code-connect"

      /**
       * -- This file was auto-generated by Code Connect --
       * \`props\` includes a mapping from Figma properties and variants to
       * suggested values. You should update this to match the props of your
       * code component, and update the \`example\` function to return the
       * code example you'd like to see in Figma
       */

      figma.connect(Test, "fake-url", {
        props: {},
        example: (props) => <Test nonOptionalProp={/* TODO */} />,
      })`,
      {
        parser: 'typescript',
        semi: false,
        trailingComma: 'all',
      },
    )

    expect(fs.writeFileSync).toHaveBeenCalledWith('test.figma.tsx', expected)
  })

  it('should nest prop mapped children in the example function', async () => {
    fs.existsSync.mockReturnValue(false)

    await createReactCodeConnect({
      destinationDir: 'test',
      destinationFile: 'test.figma.tsx',
      config: { parser: 'react' },
      mode: 'CREATE',
      normalizedName: 'Test',
      figmaConnections: [
        {
          propMapping: {
            actualPropNameForhasIcon: {
              kind: IntrinsicKind.Boolean,
              args: {
                figmaPropName: 'Has icon',
              },
            },
            children: {
              kind: IntrinsicKind.Children,
              args: {
                layers: ['Icon'],
              },
            },
          },
          reactTypeSignature: {
            actualPropNameForhasIcon: 'false | true',
            children: 'React.ReactNode',
          },
          component: {
            id: '1:1',
            figmaNodeUrl: 'fake-url',
            name: 'Test',
            type: 'COMPONENT_SET',
            componentPropertyDefinitions: {},
          },
        },
      ],
    })

    const expected = await prettier.format(
      `\
      import React from "react"
      import { Test } from "./Test"
      import figma from "@figma/code-connect"

      /**
       * -- This file was auto-generated by Code Connect --
       * \`props\` includes a mapping from your code props to Figma properties.
       * You should check this is correct, and update the \`example\` function
       * to return the code example you'd like to see in Figma
       */

      figma.connect(Test, "fake-url", {
        props: {
          // These props were automatically mapped based on your linked code:
          actualPropNameForhasIcon: figma.boolean(\"Has icon\"),
          children: figma.children(\"Icon\"),
        },
        example: (props) =>
        <Test actualPropNameForhasIcon={props.actualPropNameForhasIcon} >
          {props.children}
        </Test>,
      })`,
      {
        parser: 'typescript',
        semi: false,
        trailingComma: 'all',
      },
    )

    expect(fs.writeFileSync).toHaveBeenCalledWith('test.figma.tsx', expected)
  })

  it('Should generate the import string correctly when sourceFilepath and sourceExport are present', async () => {
    fs.existsSync.mockReturnValue(false)

    await createReactCodeConnect({
      destinationDir: path.join('src', 'components', 'figma'),
      sourceFilepath: path.join('src', 'components', 'buttons', 'PrimaryButton.tsx'),
      config: { parser: 'react' },
      mode: 'CREATE',
      normalizedName: 'MainButton',
      figmaConnections: [
        {
          sourceExport: 'default',
          component: {
            id: '1:1',
            figmaNodeUrl: 'fake-url',
            name: 'Main Button',
            type: 'COMPONENT_SET',
            componentPropertyDefinitions: {},
          },
        },
      ],
    })

    const expected = await prettier.format(
      `\
import React from "react"
import PrimaryButton from "../buttons/PrimaryButton"
import figma from "@figma/code-connect"

/**
 * -- This file was auto-generated by Code Connect --
 * \`props\` includes a mapping from Figma properties and variants to
 * suggested values. You should update this to match the props of your
 * code component, and update the \`example\` function to return the
 * code example you'd like to see in Figma
*/

figma.connect(PrimaryButton, "fake-url", {
  props: {
  },
  example: (props) => <PrimaryButton />,
})`,
      {
        parser: 'typescript',
        semi: false,
        trailingComma: 'all',
      },
    )

    expect(fs.writeFileSync).toHaveBeenCalledWith(
      path.join('src', 'components', 'figma', 'PrimaryButton.figma.tsx'),
      expected,
    )
  })
})

describe('getSetOfAllPropsReferencedInPropMapping', () => {
  it('generates set of mapped props from a PropMapping', () => {
    const result = getSetOfAllPropsReferencedInPropMapping({
      disabled: {
        kind: 'enum',
        args: {
          figmaPropName: 'Prop1',
          valueMapping: {
            Disabled: true,
            SomethingElse: {
              kind: 'boolean',
              args: {
                figmaPropName: 'Prop2',
              },
            },
          },
        },
      },
      checked: {
        kind: 'boolean',
        args: {
          figmaPropName: 'Prop3',
        },
      },
      labelText: {
        kind: 'text-content',
        args: {
          layer: 'Label',
        },
      },
    })
    expect(result).toEqual(new Set(['Prop1', 'Prop2', 'Prop3']))
  })
})

describe('generateExpressionFromIntrinsic', () => {
  it('creates a string expression', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.String,
      args: {
        figmaPropName: 'Foo',
      },
    })
    expect(result).toBe('figma.string("Foo")')
  })
  it('creates a boolean expression without ValueMapping', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.Boolean,
      args: {
        figmaPropName: 'Foo',
      },
    })
    expect(result).toBe('figma.boolean("Foo")')
  })
  it('creates a boolean expression with ValueMapping', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.Boolean,
      args: {
        figmaPropName: 'Foo',
        valueMapping: {
          true: 'Nice',
          false: undefined,
        },
      },
    })
    // Ignore whitespace diffs as this runs pre-prettier
    expect(result.replaceAll(/\s/g, '')).toBe(
      `figma.boolean("Foo", {
        "true": "Nice",
        "false": undefined
      })`.replaceAll(/\s/g, ''),
    )
  })

  it('creates an enum expression with nested ValueMapping', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.Enum,
      args: {
        figmaPropName: 'Some Variant',
        valueMapping: {
          A: 'Nice',
          B: {
            kind: IntrinsicKind.Boolean,
            args: {
              figmaPropName: 'Is True',
              valueMapping: {
                true: {
                  kind: IntrinsicKind.String,
                  args: {
                    figmaPropName: 'Label',
                  },
                },
                false: 'Something else',
              },
            },
          },
        },
      },
    })
    // Ignore whitespace diffs as this runs pre-prettier
    expect(result.replaceAll(/\s/g, '')).toBe(
      `figma.enum("Some Variant", {
        "A": "Nice",
        "B": figma.boolean("Is True", {
          "true": figma.string("Label"),
          "false": "Something else"
        })
      })`.replaceAll(/\s/g, ''),
    )
  })

  it('creates an instance expression', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.Instance,
      args: {
        figmaPropName: 'Foo',
      },
    })
    expect(result).toBe('figma.instance("Foo")')
  })

  it('creates a children expression', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.Children,
      args: {
        layers: ['Foo'],
      },
    })
    expect(result).toBe('figma.children("Foo")')
  })

  it('creates a children expression with multiple layers', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.Children,
      args: {
        layers: ['Foo', 'Bar'],
      },
    })
    expect(result).toBe('figma.children(["Foo", "Bar"])')
  })

  it('creates a textContent expression', () => {
    const result = generateExpressionFromIntrinsic({
      kind: IntrinsicKind.TextContent,
      args: {
        layer: 'Foo',
      },
    })
    expect(result).toBe('figma.textContent("Foo")')
  })
})
